﻿using System;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Interop;

using TraceView.Shared;
using TraceView.UI;
using TraceView.UI.CustomControls;

using Application = System.Windows.Application;
using Cursors = System.Windows.Input.Cursors;
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
using Rectangle = System.Windows.Shapes.Rectangle;

namespace TraceView
{
    public partial class MainWindow
    {
        #region ResizeDirection enum

        public enum ResizeDirection
        {
            Left = 1,
            Right = 2,
            Top = 3,
            TopLeft = 4,
            TopRight = 5,
            Bottom = 6,
            BottomLeft = 7,
            BottomRight = 8,
        }

        #endregion

        private const Int32 WmSyscommand = 0x112;
        private static readonly TimeSpan SDoubleClick = TimeSpan.FromMilliseconds(500);

        private DateTime _mHeaderLastClicked;
        private HwndSource _mHwndSource;
        private NotifyIcon _ni;        
        private ContextMenu menu;

        public MainWindow()
        {
            InitializeComponent();
            _ni.MouseClick += NiMouseClick;
        }
       
        protected override void OnInitialized(EventArgs e)
        {
            AllowsTransparency = false;
            ResizeMode = ResizeMode.NoResize;
            Height = 480;
            Width = 852;
            WindowStartupLocation = WindowStartupLocation.CenterScreen;
            WindowStyle = WindowStyle.None;

            SourceInitialized += HandleSourceInitialized;
            _ni = new NotifyIcon
                      {
                          BalloonTipTitle = "traceview",
                          BalloonTipText =
                              "traceview is still running, it has just been minimized.  Double click to restore to normal size",
                          Text = "traceview"
                      };


            menu = new ContextMenu();
            menu.MenuItems.Add(new MenuItem("Open", MenuOpenClick));
            menu.MenuItems.Add(new MenuItem("Exit", MenuExitClick));
            _ni.ContextMenu = menu;
            _ni.MouseDoubleClick += NiMouseDoubleClick;
            try
            {
                _ni.Icon =
                    new Icon(Assembly.GetEntryAssembly().GetManifestResourceStream("TraceView.TrayIcon.ico"));
            }
            catch (Exception ex)
            {
                Debug.Print(ex.StackTrace);
            }
            
            base.OnInitialized(e);
        }

        private void HandleSourceInitialized(Object sender, EventArgs e)
        {
            _mHwndSource = (HwndSource) PresentationSource.FromVisual(this);
            HwndSource.FromHwnd(_mHwndSource.Handle).AddHook(NativeMethods.WindowProc);
            Int32 DWMWA_NCRENDERING_POLICY = 2;
            NativeMethods.DwmSetWindowAttribute(_mHwndSource.Handle, DWMWA_NCRENDERING_POLICY,
                                                ref DWMWA_NCRENDERING_POLICY, 4);
            NativeMethods.ShowShadowUnderWindow(_mHwndSource.Handle);
        }

        [DebuggerStepThrough]
        private void HandlePreviewMouseMove(Object sender, MouseEventArgs e)
        {
            if (Mouse.LeftButton != MouseButtonState.Pressed)
            {
                Cursor = Cursors.Arrow;
            }
        }

        private void HandleHeaderPreviewMouseDown(Object sender, MouseButtonEventArgs e)
        {
            if (DateTime.Now.Subtract(_mHeaderLastClicked) <= SDoubleClick)
            {
                HandleRestoreClick(null, null);
            }

            _mHeaderLastClicked = DateTime.Now;

            if (Mouse.LeftButton == MouseButtonState.Pressed)
            {
                DragMove();
            }
        }

        private void HandleMinimizeClick(Object sender, RoutedEventArgs e)
        {
            WindowState = WindowState.Minimized;
        }

        private void HandleRestoreClick(Object sender, RoutedEventArgs e)
        {
            WindowState = (WindowState == WindowState.Normal)
                              ? WindowState.Maximized
                              : WindowState.Normal;

            m_frameGrid.IsHitTestVisible
                = WindowState == WindowState.Maximized
                      ? false
                      : true;

            m_resize.Visibility = (WindowState == WindowState.Maximized)
                                      ? Visibility.Hidden
                                      : Visibility.Visible;

            m_roundBorder.Visibility = (WindowState == WindowState.Maximized)
                                           ? Visibility.Hidden
                                           : Visibility.Visible;
        }

        private void HandleCloseClick(Object sender, RoutedEventArgs e)
        {
            Viewer.Unload();
            Application.Current.Shutdown();
        }

        private void HandleRectangleMouseMove(Object sender, MouseEventArgs e)
        {
            var clickedRectangle = (Rectangle) sender;

            switch (clickedRectangle.Name)
            {
                case "top":
                    Cursor = Cursors.SizeNS;
                    break;
                case "bottom":
                    Cursor = Cursors.SizeNS;
                    break;
                case "left":
                    Cursor = Cursors.SizeWE;
                    break;
                case "right":
                    Cursor = Cursors.SizeWE;
                    break;
                case "topLeft":
                    Cursor = Cursors.SizeNWSE;
                    break;
                case "topRight":
                    Cursor = Cursors.SizeNESW;
                    break;
                case "bottomLeft":
                    Cursor = Cursors.SizeNESW;
                    break;
                case "bottomRight":
                    Cursor = Cursors.SizeNWSE;
                    break;

                default:
                    break;
            }
        }

        private void HandleRectanglePreviewMouseDown(Object sender, MouseButtonEventArgs e)
        {
            var clickedRectangle = (Rectangle) sender;

            switch (clickedRectangle.Name)
            {
                case "top":
                    Cursor = Cursors.SizeNS;
                    ResizeWindow(ResizeDirection.Top);
                    break;
                case "bottom":
                    Cursor = Cursors.SizeNS;
                    ResizeWindow(ResizeDirection.Bottom);
                    break;
                case "left":
                    Cursor = Cursors.SizeWE;
                    ResizeWindow(ResizeDirection.Left);
                    break;
                case "right":
                    Cursor = Cursors.SizeWE;
                    ResizeWindow(ResizeDirection.Right);
                    break;
                case "topLeft":
                    Cursor = Cursors.SizeNWSE;
                    ResizeWindow(ResizeDirection.TopLeft);
                    break;
                case "topRight":
                    Cursor = Cursors.SizeNESW;
                    ResizeWindow(ResizeDirection.TopRight);
                    break;
                case "bottomLeft":
                    Cursor = Cursors.SizeNESW;
                    ResizeWindow(ResizeDirection.BottomLeft);
                    break;
                case "bottomRight":
                    Cursor = Cursors.SizeNWSE;
                    ResizeWindow(ResizeDirection.BottomRight);
                    break;

                default:
                    break;
            }
        }

        private void ResizeWindow(ResizeDirection direction)
        {
            NativeMethods.SendMessage(_mHwndSource.Handle, WmSyscommand,
                                      (IntPtr) (61440 + direction), IntPtr.Zero);
        }

        private void WindowInSysTray(bool inTray)
        {
            if (inTray)
            {
                ShowInTaskbar = false;
                WindowState = WindowState.Minimized;

                if (_ni != null)
                {
                    _ni.Visible = true;
                }
            }
            else
            {
                ShowInTaskbar = true;
                WindowState = WindowState.Normal;
                Activate();
                if (_ni != null)
                {
                    _ni.Visible = false;
                }
            }
        }

        private void WindowStateChanged(object sender, EventArgs e)
        {
            if (WindowState == WindowState.Minimized)
                WindowInSysTray(true);
        }

        private void NiMouseDoubleClick(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            WindowInSysTray(false);
        }

        private void NiMouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                //Show the context menu
                //var menu = (System.Windows.Controls.ContextMenu) FindResource("SysTrayContextMenu");
                //menu.IsOpen = true;
            }
        }

        private void MenuOpenClick(object sender, EventArgs e)
        {
            WindowInSysTray(false);
        }

        private void MenuExitClick(object sender, EventArgs e)
        {
            //Remove the system tray icon
            _ni.Dispose();
            //Shut it down
            Application.Current.Shutdown();
        }

        private void HandleMinimizeToTrayClick(object sender, RoutedEventArgs e)
        {
            WindowState = WindowState.Minimized;
            WindowInSysTray(true);
        }

        private void HandleOnNewItem(object sender, TraceEventArgs e)
        {
            if (e.NewTraceItem.Severity == Severity.Error || e.NewTraceItem.Severity == Severity.Critical)
            {
                 var toast = new TraceItemToastControl();
                 toast.Show();
                 toast.TraceItem = e.NewTraceItem;
                 toast.ToastItemClick += ToastToastItemClick;
                 toast.ToastMessage = e.NewTraceItem.View.GetToastString();
                 toast.Notify();
            }
        }

        public void ToastToastItemClick(object sender, RoutedEventArgs e)
        {
            WindowInSysTray(false);
        }

        #region Nested type: NativeMethods

        private sealed class NativeMethods
        {
            [DllImport("dwmapi.dll", PreserveSig = true)]
            internal static extern Int32 DwmSetWindowAttribute(
                IntPtr hwnd,
                Int32 attr,
                ref Int32 attrValue,
                Int32 attrSize);

            [DllImport("dwmapi.dll")]
            internal static extern Int32 DwmExtendFrameIntoClientArea(
                IntPtr hWnd,
                ref MARGINS pMarInset);

            [DllImport("user32")]
            internal static extern Boolean GetMonitorInfo(
                IntPtr hMonitor,
                MONITORINFO lpmi);

            [DllImport("User32")]
            internal static extern IntPtr MonitorFromWindow(
                IntPtr handle,
                Int32 flags);

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            internal static extern IntPtr SendMessage(
                IntPtr hWnd,
                UInt32 msg,
                IntPtr wParam,
                IntPtr lParam);

            [DebuggerStepThrough]
            internal static IntPtr WindowProc(
                IntPtr hwnd,
                Int32 msg,
                IntPtr wParam,
                IntPtr lParam,
                ref Boolean handled)
            {
                switch (msg)
                {
                    case 0x0024:
                        WmGetMinMaxInfo(hwnd, lParam);
                        handled = true;
                        break;
                }

                return (IntPtr) 0;
            }

            internal static void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
            {
                var mmi = (MINMAXINFO) Marshal.PtrToStructure(lParam, typeof (MINMAXINFO));

                // Adjust the maximized size and position to fit the work area 
                // of the correct monitor.
                Int32 MONITOR_DEFAULTTONEAREST = 0x00000002;

                IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
                if (monitor != IntPtr.Zero)
                {
                    var monitorInfo = new MONITORINFO();
                    GetMonitorInfo(monitor, monitorInfo);

                    RECT rcWorkArea = monitorInfo.m_rcWork;
                    RECT rcMonitorArea = monitorInfo.m_rcMonitor;

                    mmi.m_ptMaxPosition.m_x = Math.Abs(rcWorkArea.m_left - rcMonitorArea.m_left);
                    mmi.m_ptMaxPosition.m_y = Math.Abs(rcWorkArea.m_top - rcMonitorArea.m_top);

                    mmi.m_ptMaxSize.m_x = Math.Abs(rcWorkArea.m_right - rcWorkArea.m_left);
                    mmi.m_ptMaxSize.m_y = Math.Abs(rcWorkArea.m_bottom - rcWorkArea.m_top);
                }

                Marshal.StructureToPtr(mmi, lParam, true);
            }

            internal static void ShowShadowUnderWindow(IntPtr intPtr)
            {
                var marInset = new MARGINS
                                   {
                                       m_bottomHeight = -1,
                                       m_leftWidth = -1,
                                       m_rightWidth = -1,
                                       m_topHeight = -1
                                   };

                DwmExtendFrameIntoClientArea(intPtr, ref marInset);
            }

            #region Nested type: MARGINS

            [StructLayout(LayoutKind.Sequential)]
            internal struct MARGINS
            {
                public Int32 m_leftWidth;
                public Int32 m_rightWidth;
                public Int32 m_topHeight;
                public Int32 m_bottomHeight;
            }

            #endregion

            #region Nested type: MINMAXINFO

            [StructLayout(LayoutKind.Sequential)]
            private struct MINMAXINFO
            {
                public readonly POINT m_ptReserved;
                public POINT m_ptMaxSize;
                public POINT m_ptMaxPosition;
                public readonly POINT m_ptMinTrackSize;
                public readonly POINT m_ptMaxTrackSize;
            } ;

            #endregion

            #region Nested type: MONITORINFO

            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
            internal sealed class MONITORINFO
            {
                public Int32 m_cbSize;
                public readonly RECT m_rcMonitor;
                public readonly RECT m_rcWork;
                public Int32 m_dwFlags;

                public MONITORINFO()
                {
                    m_cbSize = Marshal.SizeOf(typeof (MONITORINFO));
                    m_rcMonitor = new RECT();
                    m_rcWork = new RECT();
                    m_dwFlags = 0;
                }
            }

            #endregion

            #region Nested type: POINT

            [StructLayout(LayoutKind.Sequential)]
            private struct POINT
            {
                public Int32 m_x;
                public Int32 m_y;

                public POINT(Int32 x, Int32 y)
                {
                    m_x = x;
                    m_y = y;
                }
            }

            #endregion

            #region Nested type: RECT

            [StructLayout(LayoutKind.Sequential, Pack = 0)]
            internal struct RECT
            {
                public static readonly RECT Empty;

                public readonly Int32 m_left;
                public readonly Int32 m_top;
                public readonly Int32 m_right;
                public readonly Int32 m_bottom;

                public RECT(Int32 left, Int32 top, Int32 right, Int32 bottom)
                {
                    m_left = left;
                    m_top = top;
                    m_right = right;
                    m_bottom = bottom;
                }

                public RECT(RECT rcSrc)
                {
                    m_left = rcSrc.m_left;
                    m_top = rcSrc.m_top;
                    m_right = rcSrc.m_right;
                    m_bottom = rcSrc.m_bottom;
                }
            }

            #endregion
        }

        #endregion
    }
}